home *** CD-ROM | disk | FTP | other *** search
/ Visual Basic Toolbox / Visual Basic Toolbox (P.I.E.)(1996).ISO / tpascal / bpvbx / tpm1.txt < prev    next >
Encoding:
Text File  |  1995-03-24  |  17.9 KB  |  358 lines

  1. Creating Visual Basic VBX's:
  2. The Basic Control
  3.  
  4. by Fred C. Hill
  5.  
  6. The first in a series of articles on the secrets of writing VBX controls in Borland Pascal
  7.  
  8. Everyone from the top down will tell you that to really program in Windows you must use C or C++.  With the release of TPW 1.0 we've all learned different, but in a lot of ways those people are correct.  The tool makers have listened and with few exceptions the tools needed to do a lot of things easy in Windows are all written for C and C++.  What a lot of developers don't realize is that with a little ingenuity a lot of those C tools can be made to work with Borland Pascal.
  9.   From the early days of Windows 3.0 (maybe even 2.0), Microsoft has been saying that to write for Windows you need to know the Software Development Kit. (SDK)  A few years ago, along with the release of Visual Basic MS introduced an SDK which was designed to produce a special type of Dynamic Load Library. (DLL)  They called this SDK, the Control Development Kit or CDK. With this kit you are able to extend the application development toolkit provided with Visual Basic.  What the kit produced when linked into your C programs was a DLL with a number of special features unique to Visual Basic and a few variations of C++.  The developer puts a .VBX file extension on it but for all intents and purposes it was still a DLL.
  10.   Custom controls extend the VB Toolbox. Your custom control can use the standard properties, events, and methods built into Visual Basic, or it could introduce some totally new ones.  You decide how your control is displayed and how its method behave. This article isn't going to teach you how to write a Custom Control, but rather how to use VBAPI_, one Pascal interface to the CDK. There are many books available describing how to write Custom Controls in C.  This article will enable you produce a VB compliant control using the language we all love.
  11.  
  12. The Steps
  13. I don't consider myself a Windows expert. I'm in the process of learning about Windows and that in itself is a huge undertaking.  What I hope to do is explain what I went through getting my first VBX to work correctly and what files I needed to place in my uses to interface with the VBAPI.OBJ file which I extracted from the VBAPI.LIB which came with Visual Basic Pro 3.0.
  14.   Before I discuss the special properties that turn a DLL into a Custom Control and what must be done to make it a Visual Basic VBX, let me explain the project we'll be undertaking here. In the CDK, Microsoft has graciously given blanket permission to use their examples in any fashion we may see fit. Microsoft has not, however, turned loose the CDK itself, so... if you don't have the CDK or a copy of VB3.0 Professional then the following code will be of little use to you.  If you're interested in turning your latest DLL project into a valuable 3rd party control then I suggest you look into getting Visual Basic.  In this article I will be explaining how to translate the basic example in the CDK, PUSH.VBX to Pascal.  This example subclasses a Windows push button and makes it your own.
  15.   The five steps necessary to write a VBX are:
  16.   1. Create your UP, Down, EGA and MONO tool buttons. These should be 28 X 28 to fit on the VB toolbar. I have included the bitmaps I used to create my version of PUSH in the files on this issue's disk.
  17.   2. Define your Property list. Of course to do this you have to have a complete design of what information your system needs.
  18.   3. Define your Event list.  Same as number 2. In addition you'll have to be sure the properties you pass back and forth are in VB strings and not Pascal or null terminated string.  VBAPI includes routines to convert null strings back and forth to VB strings.
  19.   4. Code your Control just like you would if it were a DLL. What won't work in a DLL, won't work in a VBX. Other than that, be flexible, remember that the user is (usually) providing all of your positioning data, the fonts, colors, text, etc. You merely take that information and tie it together in the context of the Control.
  20.   5. Change the extension to .VBX.  You really don't have to do this step since VB and Windows doesn't care but the customer is used to including VBX's in their projects, and besides, DLL isn't in the default selection list when adding to a project.
  21.  
  22. Pascal Custom Control File
  23. Thanks go to Microsoft for allowing unlimited use of the examples in the CDK.  Without them I'd have had a much harder time developing my controls and producing this article.
  24.  
  25. The key to writing a control is in the interface between the program and the CDK library. This interface was made available on the BPASCAL forum on COMPUSERVE. Unfortunately I had no name to associate it with and it was released to the public domain. I freely give you that interface to experiment with.  To use it you'll have to extract the VBAPI.OBJ file from the  VBAPI.LIB provided in the SDK.  Numerous library management tools exist in the various languages for that purpose.I'm not going to go into a long explanation about what is in the interface file.  Most of it is self explanatory after you examine the CDK documentation. It merely translates the C data types and VB procedure calls into Borland Pascal. One warning however, if a routine is provided in the VBAPI then use that rather than a standard Windows API.  This also allies to the messages passed between VB and your control.  Many times they appear to be equal but in fact VB won't respond properly if the wrong one is used.
  26.  
  27. The program
  28. So let's go through our sample VBX in Pascal, piece by piece.  The listings show all of the code used, but remember it is also on the disk, along with the supporting files too.
  29.   The first thing to do in include the VBAPI_ unit in you r uses list:
  30.  
  31. library PasPush;
  32. {$R push.RES}
  33. {$D Micro System Solutions - MS VB3.0 Push Demo}
  34. uses 
  35.   wintypes, 
  36.   winprocs, 
  37.   vbapi_,     
  38.   strings;
  39.  
  40. This Unit provides translation from the VBAPI.LIB (OBJ) in the CDK and at the same time it translates from VB paradigm to the Borland T(object) paradigm
  41. Next come some ID definitions (see Listing 1). Each of the bitmaps used in the control have to be created by and placed in the .RES file.  The easiest way is, of course, to use the Resource Workshop.  The IDBMP_Push bmp is the default used for the unselected picture of the bitmap and is the one used in the VB toolbar in the unselected mode. The IDBMP_PushDOWN is the selected bitmap and is only used in the toolbar when this tool is selected.  The IDBMP_PushEGA & MONO are used for EGA and non-color screens and are produced in black and white only.  Note that Autobeep in the TPush record  is the only non standard data passed between the control and the VB Form.
  42.  
  43. {Toolbox bitmap resource IDs. }
  44. const
  45.     IDBMP_Push            =  8000;
  46.     IDBMP_PushDOWN    = 8001;
  47.     IDBMP_PushMONO    = 8003;
  48.     IDBMP_PushEGA    = 8006;
  49.  
  50. // Standard Error Values}
  51.     ERR_None    = 0;
  52.     ERR_InvPropVal    = 380;
  53. { Error$(380) = "Invalid property value"}
  54. {  Procedure Declarations }
  55. {  Global Variables and Constants }
  56. {  Push control data and structs }
  57. type
  58.     PPush = ^TPush;
  59.     TPush = record
  60.         AutoBeep:     Bool;    
  61. end;
  62. Listing 1
  63.  
  64.  
  65. The Properties List
  66. The Properties list (See listing 2) is the technique used to communicate back and forth between the Visual Basic program and the Custom Control. To support a property, you declare it in the property list.  
  67.  
  68. //---------------------------------------------------------------------------}
  69. const
  70.     IPROP_Push_NAME  = $0000;
  71.     IPROP_Push_INDEX = $0001;
  72.     IPROP_Push_PARENT = $0002;
  73.     IPROP_Push_BACKCOLOR = $0003;
  74.     IPROP_Push_LEFT = $0004;
  75.     IPROP_Push_TOP = $0005;
  76.     IPROP_Push_WIDTH = $0006;
  77.     IPROP_Push_HEIGHT = $0007;
  78.     IPROP_Push_ENABLED = $0008;
  79.     IPROP_Push_VISIBLE = $0009;
  80.     IPROP_Push_MOUSEPOINTER = $000A;
  81.     IPROP_Push_CAPTION = $000B;
  82.     IPROP_Push_FONTNAME = $000C;
  83.     IPROP_Push_FONTSIZE = $000D;
  84.     IPROP_Push_FONTBOLD = $000E;
  85.     IPROP_Push_FONTITALIC = $000F;
  86.     IPROP_Push_FONTSTRIKE = $0010;
  87.     IPROP_Push_FONTUNDER = $0011;
  88.     IPROP_Push_DRAG = $0012;
  89.     IPROP_Push_DRAGICON = $0013;
  90.     IPROP_Push_TABINDEX = $0014;
  91.     IPROP_Push_TABSTOP = $0015;
  92.     IPROP_Push_TAG = $0016;
  93.     IPROP_Push_AutoBeep = $0017;
  94.  
  95.     AutoBeepName : array[0..8] of Char = 'AutoBeep'#0;
  96.  
  97. Property_AutoBeep: tPROPINFO  = (
  98.     npszName :  tOffset(@AutoBeepName);
  99.     fl :    DT_Bool or PF_fGetData or         
  100.         PF_fSetData or         
  101.         PF_fSaveData;
  102.     offsetData        :  0;
  103.     infoData        :  0;
  104.     dataDefault        :  0;
  105.     npszEnumList    :  0;
  106.     enumMax        :  0  );
  107. const
  108.     PropListPush : array[0..24]of         ofsPPROPINFO = (
  109.     pPROPInfo_STD_CTLNAME,
  110.     PPROPINFO_STD_INDEX,
  111.     PPROPINFO_STD_PARENT,
  112.     PPROPINFO_STD_BACKCOLOR,
  113.     PPROPINFO_STD_LEFT,
  114.     PPROPINFO_STD_TOP,
  115.     PPROPINFO_STD_WIDTH,
  116.     PPROPINFO_STD_HEIGHT,
  117.     PPROPINFO_STD_ENABLED,
  118.     PPROPINFO_STD_VISIBLE,
  119.     PPROPINFO_STD_MOUSEPOINTER,
  120.     PPROPINFO_STD_CAPTION,
  121.     PPROPINFO_STD_FONTNAME,
  122.     PPROPINFO_STD_FONTSIZE,
  123.     PPROPINFO_STD_FONTBOLD,
  124.     PPROPINFO_STD_FONTITALIC,
  125.     PPROPINFO_STD_FONTSTRIKE,
  126.     PPROPINFO_STD_FONTUNDER,
  127.     PPROPINFO_STD_DRAGMODE,
  128.     PPROPINFO_STD_DRAGICON,
  129.     PPROPINFO_STD_TABINDEX,
  130.     PPROPINFO_STD_TABSTOP,
  131.     PPROPINFO_STD_TAG,
  132.     ofsPPropInfo(@Property_AutoBeep),    
  133.     { point to non-standard property}
  134.     0);
  135. Listing 2.
  136.  
  137. Standard properties are those which are supported by the VB runtime.  Most of the properties in this list are standard Visual Basic properties. Non standard are those you wish to support and for which you have written supporting code.  The one non-standard property in this list is the beep property which will be used to provide a sound to the button.
  138.   The tPROPINFO structure defines the data and the processing used for the non-standard property.  In this case the property name, used in the VB property list is located at tOffset@AutoBeepName (see above).  The field is boolean and the VB runtime will get the data (PF_fGetData) directly from the record structure pointed to by the model structure. VB will also put the data (PF_fSetData) directly into the save structure. PF_fSaveData tells VB to get the value from the record structure and write it disk when the Form is saved.  Additional (and different) flag values can make VB act differently to different data.
  139.  
  140. The Event List
  141. The Event list (See Listing 3) is the method by which the Custom Control interfaces with the VB program.  The Event driven interface is what makes VB such a powerful development environment. The VB run-time will handle many standard events for you, while still giving you the option to override VB. In addition to the standard events you can add and fire your own events based on what your control is doing and what it needs from the calling VB program.
  142.   In the following list are three of the most rudimentary events required for a Custom Control. The Click event which is required for an object to gain the focus, and the DragDrop/DragOver events which allow the object to be placed and moved about the form.  We'll see these and others in operation as we well.
  143. The event name, EventClick Name, is made by VB into the subroutine / function name,  for example: 
  144. Sub PasButton1_Click(ButtonCaption as String) VB uses the event parameter EventClickParm, include parameters in the subroutine call.
  145.  
  146. type
  147. TParams = record
  148.     ClickString:     HLStr;
  149.     Index : Pointer;    { Reserve space for index parameter to array ctl}
  150.     end;
  151.  
  152. { Event list  Define the consecutive indicies for the events
  153. const
  154.     EVENT_PUSH_CLICK    = 0;
  155.     EVENT_PUSH_DRAGDROP    = 1;
  156.     EVENT_PUSH_DRAGOVER    = 2;
  157.     EVENT_PUSH_GOTFOCUS    = 3;
  158.     EVENT_PUSH_KEYDOWN    = 4;
  159.     EVENT_PUSH_KEYPRESS    = 5;
  160.     EVENT_PUSH_KEYUP        = 6;
  161.     EVENT_PUSH_LOSTFOCUS    = 7;
  162.  
  163. Event procedure parameter prototypes
  164. Parms_SD : array[0..0] of word = (ET_I2);            {Integer data type }
  165.  
  166. EventClickName : array[0..8] of Char =         'Click'#0;
  167. EventClickParm: array[0..24] of char = 'ButtonCaption as String'#0;
  168.  
  169. Event_Click: tEVENTINFO  = (
  170.     npszName:         tOffset(@EventClickName);
  171.     cParms:        1;
  172.     cwParms:         2;
  173.     npParmTypes:    tOffset(@Parms_SD);
  174.     npszParmProf:    tOffSet(@EventClickParm);
  175.     fl:                    0
  176. );
  177.  
  178. EventListPush: array[0..8]of ofsPEVENTInfo = (
  179.     ofsPEventInfo(@Event_Click),
  180.     PEVENTINFO_STD_DRAGDROP,
  181.     PEVENTINFO_STD_DRAGOVER,
  182.     PEVENTINFO_STD_GOTFOCUS,
  183.     PEVENTINFO_STD_KEYDOWN,
  184.     PEVENTINFO_STD_KEYPRESS,
  185.     PEVENTINFO_STD_KEYUP,
  186.     PEVENTINFO_STD_LOSTFOCUS,
  187.     0);
  188.  
  189. EventListPush: array[0..8]of ofsPEVENTInfo = (
  190.     ofsPEventInfo(@Event_Click),
  191.     PEVENTINFO_STD_DRAGDROP,
  192.     PEVENTINFO_STD_DRAGOVER,
  193.     PEVENTINFO_STD_GOTFOCUS,
  194.     PEVENTINFO_STD_KEYDOWN,
  195.     PEVENTINFO_STD_KEYPRESS,
  196.     PEVENTINFO_STD_KEYUP,
  197.     PEVENTINFO_STD_LOSTFOCUS,
  198.     0
  199.  
  200. Listing 3
  201.  
  202. function PushCtlProc( Control: HCtl;Wnd: HWnd;  Msg,  WParam: Word;LParam: LongInt ) : LongInt; export;
  203. var
  204.  Params:    TParams;
  205.  StrBuf:    array[0..19]of char;
  206.  Caption:    Integer;
  207.  error:    Err;
  208.  tmpStr:    PChar;
  209.   Push:    PPush;
  210. begin
  211.  Push := PPush(VBDerefControl( Control));
  212.  case Msg of
  213.    VBM_MNEMONIC,
  214.    VBN_COMMAND: begin
  215.      if Msg = VBM_MNEMONIC then          { Act like a click}
  216.     LParam := MAKELONG( 0,     BN_CLICKED);
  217.     case HIWORD(LParam) of
  218.       BN_CLICKED: begin
  219.         Caption := GetWindowText( Wnd, StrBuf, 20);
  220.         Params.ClickString := VBCreateHlstr( @StrBuf, Caption);
  221.         if Push^.AutoBeep then
  222.                  Messagebeep(0);
  223.                 error := VBFireEvent( Control,EVENT_Push_Click,@Params);
  224.             VBDestroyHlstr( Params. ClickString);
  225.         end;
  226.         end;
  227.         PushCtlProc := 0;
  228.         exit;
  229.         end;
  230.     VBM_SETPROPERTY:
  231.     case WParam of
  232.     IPROP_PUSH_CAPTION:  
  233.         { To avoid a Windows problem, make sure text is under 255 bytes:}
  234.         if (lstrlen(PChar(LParam)) > 255) then
  235.                 PChar(LParam)[255] := #0;
  236.     end;
  237.   end;
  238.     {// Default processing:}
  239.     PushCtlProc := VBDefControlProc(Control, Wnd, Msg, WParam, LParam);
  240. end;
  241.  
  242. Listing 4
  243.  
  244. Control Procedure
  245. This is the main control procedure for the Custom Control (See Listing 4). There can be multiple control procedure if your Custom Control implements multiple control objects. The model structure (described later) will reference this procedure.
  246.  
  247. The Model Structure
  248. This defines the control model (using the event and property structures). It is defined after the control proc because it must reference it in the parameter list. This is probably the most important structure in the program. This is used to tell the Visual Basic runtime what you are and where your various internal structures are located. See Listing 5
  249.  
  250. Register Custom Control
  251. The control is registered by the function VBINITCC (See Listing 6), which is called by VB when the custom control DLL is loaded for use. After this the exports are defined, and that's it!
  252.  
  253. Finishing Off
  254. Listing 7 shows the Visual Basic make file required, and Listing 8 is the VB form.
  255.  That's all there is to creating a VBX in Borland Pascal. In the next installment, I'll take the basic code I developed here and turn it into a really useful control I call DiskTool.  It gets information from the drives on your system, information VB can't directly get, and passes that information to your Visual Basic form.  Two interesting  features in the control are the ability to include an enumerated field in the property list for the drive type, and the ability to produce a development time only tool.
  256.  
  257. Copyright ⌐ 1994 Fred C. Hill, all rights reserved
  258.  
  259. const
  260. ModelDefCtlName:  array[0..8] of Char = 'PasPush'#0;     { default control name prefix}
  261.   ModelClassName: array[0..14] of Char = 'PasPushButton'#0;     { Visual Basic class name}
  262.   ModelParentClassName: array[0..8] of    Char = 'Button'#0;    { Parent window class if subclassed}
  263.   modelPush: TMODEL = (
  264.     usVersion:    VB_VERSION;                        { VB version used by control}
  265.     fl:    MODEL_fFocusOk or MODEL_fMnemonic;     { Bitfield structure}
  266.     ctlproc:    TFarProc(@PushCtlProc);        { The control proc.}
  267.     fsClassStyle:    cs_VRedraw or cs_HRedraw;    { window class style}
  268.     flWndStyle:        BS_PUSHBUTTON;         { default window style}
  269.     cbCtlExtra:        sizeof(TPush);                { # bytes alloc'd for HCTL structure}
  270.     idBmpPalette:    IDBMP_Push;                    { BITMAP id for tool palette}
  271.     DefCtlName: tOffset(@ModelDefCtlName);     { default control name prefix}
  272.     ClassName: tOffset(@ModelClassName);        { Visual Basic class name}
  273.     ParentClassName:    tOffset(@ModelParentClassName);    { Parent window class if subclassed}
  274.     proplist:        ofs(PropListPush);            { Property list}
  275.     eventlist:        ofs(EventListPush);            { Event list}
  276.     nDefProp:         0;                                { index of default property}
  277.     nDefEvent:        0 );                            { index of default event}
  278.  
  279.  
  280. Listing 5
  281.  
  282.  
  283. function VBINITCC(usVersion: Word;   fRunTime: Boolean): Boolean; export;
  284. begin
  285.   VBINITCC := VBRegisterModel(         HInstance, modelPush);
  286. end;
  287.  
  288. exports
  289.   VBINITCC index 2,
  290.   PushCtlProc index 3;
  291.  
  292. begin
  293. end;
  294. Listing 6
  295.  
  296. TPWPUSH.FRM
  297. TPW_PUSH.VBX
  298. ProjWinSize=152,402,248,215
  299. ProjWinShow=2
  300.  
  301. Listing 7
  302.  
  303. Listing 8
  304.  
  305. VERSION 2.00
  306. Begin Form Form1 
  307. Caption = "Visual Basic VBX in Borland Pascal"
  308.    ClientHeight    =   1605
  309.    ClientLeft      =   1710
  310.    ClientTop       =   3645
  311.    ClientWidth     =   6105
  312.    Height          =   2010
  313.    Left            =   1650
  314.    LinkTopic       =   "Form1"
  315.    ScaleHeight     =   1605
  316.    ScaleWidth      =   6105
  317.    Top             =   3300
  318.    Width           =   6225
  319.    Begin PasPushButton PasPush2 
  320.       AutoBeep        =   0   'False
  321.       Caption         =   "E&xit"
  322.       Height          =   375
  323.       Left            =   2760
  324.       TabIndex        =   2
  325.       Top             =   960
  326.       Width           =   1815
  327.    End
  328.    Begin PasPushButton PasPush1 
  329.       AutoBeep        =   0   'False
  330.       Caption         =   "&Test me"
  331.       Height          =   375
  332.       Left            =   480
  333.       TabIndex        =   1
  334.       Top             =   960
  335.       Width           =   2055
  336.    End
  337.    Begin TextBox Text1 
  338.       Height          =   495
  339.       Left            =   480
  340.       TabIndex        =   0
  341.       Text            =   "Text1"
  342.       Top             =   240
  343.       Width           =   2175
  344.    End
  345. End
  346. Option Explicit
  347. Sub PasPush1_Click (ButtonCaption As String)
  348.     Text1 = PasPush1.Caption
  349. End Sub
  350.  
  351. Sub PasPush2_Click (ButtonCaption As String)
  352.     Unload Me
  353. End
  354.  
  355.  
  356.  
  357. Fred Hill is president of Micro System Solutions. His company produces DOS and Windows applications and tools in Borland Pascal 7.0 and Visual Basic 3.0.
  358.